#!/usr/bin/env jruby
# -*- coding: us-ascii -*-

require 'test/unit'
dir = File.dirname(__FILE__)
require File.join(dir, '..', '..', 'lib', 'auxil', 'sortedstable')

########################################

def r() ; rand(100) ; end

module Constants4SortedStableTests
  LAMBDA1 = lambda { |x| x }
  
  INTEGER = 5
  FLOAT = 5.0
  STRING = 'foo'
  ARRAY = %w'foo bar baz'
  
  POS = rand(50) + 1 ; raise 'NEED POSITIVE CONSTANT!' unless POS > 0
  ZERO = 0
  NEG = -rand(50) - 1 ; raise 'NEED NEGATIVE CONSTANT!' unless NEG < 0
  
  RAND8 = [r, r, r, r, r, r, r, r]
end

########################################

class TestSortedStableConstructor < Test::Unit::TestCase
  include Constants4SortedStableTests
  
  def test_should_accept_positive_integer
    assert_nothing_raised { SortedStable.new POS, LAMBDA1 }
  end
  
  def test_should_accept_lambda_arity_1
    assert_nothing_raised { SortedStable.new POS, LAMBDA1 }
  end
  
  def test_should_reject_noninteger
    assert_raise(TypeError, 'f') { SortedStable.new FLOAT, LAMBDA1 }
    assert_raise(TypeError, 's') { SortedStable.new STRING, LAMBDA1 }
    assert_raise(TypeError, 'a') { SortedStable.new ARRAY, LAMBDA1 }
  end
  
  def test_should_reject_nonpositive_integer
    assert_raise(RangeError, '0') { SortedStable.new ZERO, LAMBDA1 }
    assert_raise(RangeError, 'n') { SortedStable.new NEG, LAMBDA1 }
  end
  
  def test_should_reject_lacking_lambda
    assert_raise(ArgumentError) { SortedStable.new POS }
  end
  
  def test_should_reject_lambda_arity_non1
    assert_raise(ArgumentError) { SortedStable.new POS, 
      lambda { 'foo' } }
    assert_raise(ArgumentError) { SortedStable.new POS, 
      lambda { |x,y| x<y } }
  end
end

########################################

class TestSortedStableSizeInvariants < Test::Unit::TestCase
  include Constants4SortedStableTests
  
  def test_should_not_mutate_input
    ss = SortedStable.new(5, LAMBDA1)
    rm_copy = Marshal.load(Marshal.dump(RAND8))
    RAND8.each { |n| ss << n }
    assert_equal rm_copy, RAND8
  end
  
  def test_invariant1
    ss = SortedStable.new(1, LAMBDA1)
    assert ss.size <= ss.size_limit, 'invariant start'
    rand(10) { ss << rand(100) }
    assert ss.size <= ss.size_limit, 'invariant end'
  end
  
  def test_invariant5
    ss = SortedStable.new(5, LAMBDA1)
    assert ss.size <= ss.size_limit, 'invariant start'
    rand(10) { ss << rand(100) }
    assert ss.size <= ss.size_limit, 'invariant end'
  end
  
  def test_999_limit_should_not_make_size_exceed_smaller_input_size
    ss = SortedStable.new(999, LAMBDA1)
    RAND8.each { |n| ss << n }
    assert_equal RAND8.size, ss.size
  end
  
  def test_999_limit_should_not_make_size_exceed_1
    ss = SortedStable.new(999, LAMBDA1)
    a = [rand(100)]
    a.each { |n| ss << n }
    assert_equal a.size, ss.size
  end
  
  def test_999_limit_should_not_make_size_exceed_0
    ss = SortedStable.new(999, LAMBDA1)
    [].each { |n| ss << n }
    assert ss.empty?
  end
end

########################################

class TestSortedStableMinMaxInvariants < Test::Unit::TestCase
  include Constants4SortedStableTests
  
  SORT_BY_POSITIVE = lambda { |x| x }
  NUMBERS = ['ii', 'aa', 'uu', 'oo', 'ee']
  
  def setup
    @ss = SortedStable.new(NUMBERS.size, SORT_BY_POSITIVE)
    NUMBERS.each { |n| @ss << n }
  end
  
  def test_should_be_in_correct_order
    assert_equal NUMBERS.sort.reverse, @ss
  end
  
  def test_max_should_be_first
    assert_equal 'uu', @ss.first
    assert @ss.first == @ss.max
  end
  
  def test_min_should_be_last
    assert_equal 'aa', @ss.last
    assert @ss.last == @ss.min
  end
end

########################################

class TestSortedStableFun < Test::Unit::TestCase
  include Constants4SortedStableTests
  
  SORT_BY_POSITIVE = lambda { |x| x }
  SORT_BY_NEGATIVE = lambda { |x| -x }
  
  def test_should_find_max_of_8
    ss = SortedStable.new(1, SORT_BY_POSITIVE)
    RAND8.each { |n| ss << n }
    assert_equal RAND8.max, ss[0]
  end
  
  def test_should_find_min_of_8
    ss = SortedStable.new(1, SORT_BY_NEGATIVE)
    RAND8.each { |n| ss << n }
    assert_equal RAND8.min, ss[0]
  end
  
  def test_should_find_hightest_3_of_8
    ss = SortedStable.new(3, SORT_BY_POSITIVE)
    RAND8.each { |n| ss << n }
    sorted8 = RAND8.sort
    3.times do
      n = sorted8.pop
      assert ss.include?(n), 
        "#{n} from #{RAND8.inspect} not in #{ss.inspect}"
    end
  end
  
  def test_should_find_lowest_3_of_8
    ss = SortedStable.new(3, SORT_BY_NEGATIVE)
    RAND8.each { |n| ss << n }
    sorted8 = RAND8.sort.reverse
    3.times do
      n = sorted8.pop
      assert ss.include?(n), 
        "#{n} from #{RAND8.inspect} not in #{ss.inspect}"
    end
  end
end
